package crystal.workshop.io;

import java.io.File;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

import mujl.io.FileReaderManager;
import mujl.io.FileReaderManager.Mode;
import crystal.lib.StructureData;
import crystal.workshop.SimpleStructureData;
import crystal.workshop.StructureSource;
import crystal.workshop.ui.StructureDataModel;
import crystal.workshop.ui.StructuresListModel;

/**
 * Provides methods to load structure files from the disk and keeps a global
 * list of all structure files loaded.  It also provides querying capabilities
 * to allow external callers to get information such as the filename of a
 * structure from a structure data model instance.
 * 
 * @author Martin Uhrin
 *
 */
public class DiskReader implements StructureSource {

	protected FileReaderManager<StructureData>	structureReader =
		new FileReaderManager<StructureData>(Mode.EXTENSION);
	
	/** A hash table linking each structure instance to the its disk
	 * information. */
	private Hashtable<SimpleStructureData, LocalDiskStructureInfo> structures =
		new Hashtable<SimpleStructureData, LocalDiskStructureInfo>();
	
	/** When calling for a structure update, should it only be updated if the
	 * timestamp has changed. */
	private boolean useTimestampChecking = true;
	
	/**
	 * When loading structures is the timestamp checked to see if the structure
	 * has been modified since is was last loaded.
	 * @return if timestamp checking is enabled.
	 */
	public boolean getUseTimestampChecking() { return useTimestampChecking; }
	
	/**
	 * Enable or disable use of timestamp to check if structure has been
	 * modified since last update.
	 * @param use true to enable, false to disable.
	 */
	public void setUseTimestampChecking( boolean use ) { useTimestampChecking = use; }
	
	public FileReaderManager<StructureData> getFileReaderManager() { return structureReader; }
	
	@Override
	public String getName() {
		return "Local disk source";
	}
	
	@Override
	public int getNumberOfStructures() {
		return structures.size();
	}
	
	@Override
	public Collection<SimpleStructureData> getStructures() {
		return structures.keySet();
	}
	
	@Override
	public StructuresListModel getStructuresList() {
		return null;
	}
	
	public void updateAll() {
		final Enumeration<SimpleStructureData> structs = structures.keys();

		// Update the structures
		while( structs.hasMoreElements() ) {
			update(structs.nextElement());
		}
	}

	public boolean update(final StructureDataModel struct) {
		final LocalDiskStructureInfo strInfo = structures.get(struct);
		if( strInfo == null ) return false;
		
		if( useTimestampChecking &&
				strInfo.getLastRead() < strInfo.getStructureFile().lastModified() ) {
			return false;
		}

		// Update the crystal structure
		final StructureData sd = structureReader.readFile(strInfo.getStructureFile());
		struct.setCrystal(sd.getCrystal());
		
		return true;
	}

	
	/**
	 * Load a structure file from disk.  When the caller is finished with this structure
	 * they should call <code>unloadStructure</code>
	 * @param strFile the structure file to load.
	 */
	synchronized public StructureDataModel loadStructure(final File strFile) {
		SimpleStructureData s;
		if((s = getStructure(strFile)) != null) {
			structures.get(s).increaseReferenceCount();
		} else {
			// TODO Add some checking here, maybe an exception thrown
			 s = new SimpleStructureData( strFile.toString(), structureReader.readFile( strFile ) );
			
			structures.put(s, new LocalDiskStructureInfo(strFile, System.currentTimeMillis()));
		}
		
		return s;
	}
	
	/**
	 * Unload the structure.  This will decrease the structures reference count
	 * and remove the file from the structures list when it reaches 0.
	 * @param str the structure to unload
	 */
	synchronized public void unloadStructure(final StructureDataModel str) {
		if(str == null)
			return;
		
		if(structures.get(str).decreaseReferenceCount() == 0) {
			structures.remove(str);
		}
	}
	
	public File getStructureFile(final StructureDataModel str) {
		File strFile = null;
		final LocalDiskStructureInfo strInfo = structures.get(str);
		if(strInfo != null)
			strFile = strInfo.getStructureFile();
		return strFile;
	}
	
	/**
	 * Get a structure from a file.  This is used internally when checking if a
	 * particular file has already been loaded.
	 * 
	 * @param strFile the structure file
	 * @return the structure instance or null if not found
	 */
	protected SimpleStructureData getStructure(final File strFile) {
		SimpleStructureData str = null;
		
		// Rather complicated way to find the correct structure correspond to this
		// files but it will do the job
		Map.Entry<SimpleStructureData, LocalDiskStructureInfo> entry = null;
		for( final Iterator<Map.Entry<SimpleStructureData, LocalDiskStructureInfo>> iter =
			structures.entrySet().iterator(); iter.hasNext();) {
			
			entry = iter.next();
			if(entry.getValue().getStructureFile().equals(strFile)) {
				str = entry.getKey();
				break;
			}
		}
		
		return str;
	}
}
